package com.atguigu.gulimall.product.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catalog2Level;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //查询所有分类
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        //组装成父子的树形结构
        List<CategoryEntity> collect = categoryEntities.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(categoryEntity -> {
                    categoryEntity.setChildren(setCategoryEntityChildren(categoryEntity, categoryEntities));
                    return categoryEntity;
                })
                .sorted((c1, c2) -> (c1.getSort() == null ? 0 : c1.getSort()) - (c2.getSort() == null ? 0 : c2.getSort()))
                .collect(Collectors.toList());
        return collect;
    }

    @Override
    public void deleteMenuByIds(List<Long> asList) {
        //TODO 查看哪些菜单被引用
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    @Cacheable(value = "catalog",key = "#root.method.name")
    public List<CategoryEntity> getCategoryLevel1() {
        System.out.println("获取一级分类");
        List<CategoryEntity> categoryEntityList = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return categoryEntityList;
    }

    public Map<String, List<Catalog2Level>> getCatalogJsonSentinelResource(BlockException e){
        log.info("getCatalogJsonSentinelResource===================");
        return null;
    }

    @Override
    //@Cacheable(value = "catalog",key = "#root.method.name")
    @SentinelResource(value = "getCatalogJsonSentinelResource",blockHandler = "getCatalogJsonSentinelResource")
    public Map<String, List<Catalog2Level>> getCatalogJson() {

        System.out.println("查询了数据库");

        List<CategoryEntity> categoryEntityList = this.list(null);

        //查出所有一级分类
        List<CategoryEntity> categoryById = getCategoryById(categoryEntityList, 0L);

        Map<String, List<Catalog2Level>> collect = categoryById.stream().collect(Collectors.toMap(k -> {
            return k.getCatId().toString();
        }, categoryEntity -> {

            List<CategoryEntity> categoryEntities = getCategoryById(categoryEntityList, categoryEntity.getCatId());

            List<Catalog2Level> catalog2Levels = categoryEntities.stream().map(categoryEntity1 -> {
                Catalog2Level catalog2Level = new Catalog2Level();
                catalog2Level.setCatalog1Id(categoryEntity.getCatId());
                catalog2Level.setId(categoryEntity1.getCatId());
                catalog2Level.setName(categoryEntity1.getName());
                List<CategoryEntity> categoryEntityList1 = getCategoryById(categoryEntityList, categoryEntity1.getCatId());

                List<Catalog2Level.Catalog3Level> catalog3LevelList = categoryEntityList1.stream().map(categoryEntity2 -> {
                    Catalog2Level.Catalog3Level catalog3Level = new Catalog2Level.Catalog3Level();
                    catalog3Level.setCatalog2Id(categoryEntity1.getCatId());
                    catalog3Level.setId(categoryEntity2.getCatId());
                    catalog3Level.setName(categoryEntity2.getName());
                    return catalog3Level;
                }).collect(Collectors.toList());

                catalog2Level.setCatalog3List(catalog3LevelList);
                return catalog2Level;
            }).collect(Collectors.toList());

            return catalog2Levels;

        }));
        return collect;
    }

    public Map<String, List<Catalog2Level>> getCatalogJson2() {
        return getCatalogJsonRedisson();
    }


    public Map<String, List<Catalog2Level>> getCatalogJsonRedisson() {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        Map<String, List<Catalog2Level>> catalogJsonFromDB = null;
        if(lock){
            try {
                System.out.println("获取分布式锁成功");
                catalogJsonFromDB = getCatalogJsonFromDB();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class), Arrays.asList("lock"),uuid);
            }
            return catalogJsonFromDB;
        }else {
            System.out.println("获取分布式锁失败，等待重试");
            return getCatalogJsonRedisson();
        }
    }

    public Map<String, List<Catalog2Level>> getCatalogJsonLocalLock() {
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        String catalogJson = opsForValue.get("CatalogJson");
        Map<String, List<Catalog2Level>> catalogJsonFromDB = null;

        if (StringUtils.isEmpty(catalogJson)) {

            System.out.println("缓存没有命中");
            synchronized (this){
                return getCatalogJsonFromDB();
            }
        }
        System.out.println("缓存命中");
        catalogJsonFromDB = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Level>>>() {
        });
        return catalogJsonFromDB;
    }

    public Map<String, List<Catalog2Level>> getCatalogJsonFromDB() {

        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        String catalogJson = opsForValue.get("CatalogJson");
        if (!StringUtils.isEmpty(catalogJson)) {
            return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Level>>>() {
            });
        }

        System.out.println("查询了数据库");


        List<CategoryEntity> categoryEntityList = this.list(null);

        //查出所有一级分类
        List<CategoryEntity> categoryById = getCategoryById(categoryEntityList, 0L);

        Map<String, List<Catalog2Level>> collect = categoryById.stream().collect(Collectors.toMap(k -> {
            return k.getCatId().toString();
        }, categoryEntity -> {

            List<CategoryEntity> categoryEntities = getCategoryById(categoryEntityList, categoryEntity.getCatId());

            List<Catalog2Level> catalog2Levels = categoryEntities.stream().map(categoryEntity1 -> {
                Catalog2Level catalog2Level = new Catalog2Level();
                catalog2Level.setCatalog1Id(categoryEntity.getCatId());
                catalog2Level.setId(categoryEntity1.getCatId());
                catalog2Level.setName(categoryEntity1.getName());
                List<CategoryEntity> categoryEntityList1 = getCategoryById(categoryEntityList, categoryEntity1.getCatId());

                List<Catalog2Level.Catalog3Level> catalog3LevelList = categoryEntityList1.stream().map(categoryEntity2 -> {
                    Catalog2Level.Catalog3Level catalog3Level = new Catalog2Level.Catalog3Level();
                    catalog3Level.setCatalog2Id(categoryEntity1.getCatId());
                    catalog3Level.setId(categoryEntity2.getCatId());
                    catalog3Level.setName(categoryEntity2.getName());
                    return catalog3Level;
                }).collect(Collectors.toList());

                catalog2Level.setCatalog3List(catalog3LevelList);
                return catalog2Level;
            }).collect(Collectors.toList());

            return catalog2Levels;

        }));
        String toJSONString = JSON.toJSONString(collect);
        opsForValue.set("CatalogJson", toJSONString, 1, TimeUnit.DAYS);
        return collect;

    }

    private List<CategoryEntity> getCategoryById(List<CategoryEntity> categoryEntityList, Long catId) {
        List<CategoryEntity> parent_cid = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", catId));
        return parent_cid;
    }


    //装载所有子类
    private List<CategoryEntity> setCategoryEntityChildren(CategoryEntity categoryEntity, List<CategoryEntity> all) {
        List<CategoryEntity> collect = all.stream()
                .filter(categoryEntity1 -> categoryEntity1.getParentCid() == categoryEntity.getCatId())
                .map(categoryEntity1 -> {
                    categoryEntity1.setChildren(setCategoryEntityChildren(categoryEntity1, all));
                    return categoryEntity1;
                })
                .sorted((c1, c2) -> (c1.getSort() == null ? 0 : c1.getSort()) - (c2.getSort() == null ? 0 : c2.getSort()))
                .collect(Collectors.toList());
        return collect;

    }

}